/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include "CLinuxSerialDriver.h"


CLinuxSerialDriver::CLinuxSerialDriver(){
	portHandle = -1;
}
CLinuxSerialDriver::~CLinuxSerialDriver(){
}

bool CLinuxSerialDriver::Open(const char *serialport, int baudrate,int dataBits,int parity, int stopBits){
  //char serial_name[50];
	
	if(portHandle > 0){
		fprintf(stderr,"CLinuxSerialDriver::Open(): Already opened! Close the port first!\n");
		return false;
	}
	//sprintf(serial_name,"/dev/ttyS%d",serial_port);
	
	if(!openSerialPortHandle(serialport)){
		fprintf(stderr,"CLinuxSerialDriver::Open(): Failed to Open port %s  \n",serialport);
		return false;
	}
	if(setCommParameters(baudrate,dataBits,parity, stopBits )!=0){
		fprintf(stderr,"CLinuxSerialDriver::Open(): Failed to set port parameters for %s\n",serialport);
		return false;
	}
	return true;
}

bool CLinuxSerialDriver::Close(){
	close(portHandle);
	portHandle = -1;
  return true;
}

/**
 * Clears the serial buffer
 * @return true on success, else false
 */
bool CLinuxSerialDriver::Clear(){
	tcflush(portHandle, TCIFLUSH);
  return true;
}
		
bool CLinuxSerialDriver::ReadChar(unsigned char &c){
  fd_set readfs;    /* file descriptor set */
	FD_SET(portHandle, &readfs);  /*set testing for source*/
	
	if (FD_ISSET(portHandle, &readfs)){
		int res = read(portHandle, &c, 1);
		if(res == 1) return true;
	}
	
	return false;
}

/**
* Read the data from the serial. 
* The port is selected and then buffer read one character a time until the length is reached or 
* the buffer is emptied and the number of retries is exceeded
* 
* @p *inbuf buffer that stores the input characthers (user must initialize)
* @p length the # of bytes of wanted to receive
*
* @return     -1 on timeout, else the number of bytes read. 
*/
int CLinuxSerialDriver::Read(char *inbuf, int length) {

	int n, bytesRead, attempts;
	char inchar;
	int portCount;
	int maxPorts;
	struct timeval timeout;
	fd_set readfs;    /* file descriptor set */

	int TIMEOUT = 50000;      /* time to wait for port to respond, in microseconds */
	int MAXATTEMPTS = 200;    /* maximum number of attempts to read characters */
	int WAITCHARTIME = 1000;  /* time to wait for a char to arrive. */

	/* select will wait for port to respond or timeout */
	maxPorts = portHandle+1;
	timeout.tv_usec = TIMEOUT;  /* microseconds */
	timeout.tv_sec  = 0;        /* seconds */
	FD_ZERO(&readfs);
	FD_SET(portHandle, &readfs);  /* set testing for portHandle */

	portCount = select(maxPorts, &readfs, NULL, NULL, &timeout);  /* block until input becomes available */
	if ((portCount==0) || (!FD_ISSET(portHandle, &readfs))) {
		return -1;
	}
  /* Read data into the response buffer.
	* until we get enough data or exceed the maximum
	* number of attempts
	*/
	bytesRead = 0;
	attempts = 0;
	while (bytesRead < length && attempts++ < MAXATTEMPTS) {
		n = read(3, &inchar, 1);
		if (n == 1)
			inbuf[bytesRead++] = inchar;
		else
			usleep(WAITCHARTIME);  /* sleep a while for next byte. */
	}
	
	return bytesRead;
}

/**
 * Writes data to serial
 *
 * @p data      data to be written
 * @p length    the length of the data
 * @return      true in success, else false
 */
bool CLinuxSerialDriver::Write(char* data, int length)
{
	int status;

	/* write command to the serial port */
	status = write(portHandle, data, length);
	if (status >=0) {
		return true;
	} else {
		return false; 
	}
}


///////////////////////////////////////////////////////////////////////////////7
////////////////////// PRIVATES ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////7
/**
* Opens serial port 
* @p port The serial port folder "/dev/ttyS0" 
* @return true if success, else false
*/
bool CLinuxSerialDriver::openSerialPortHandle(const char *port){
	fprintf(stderr,"CLinuxSerialDriver::openSerialPortHandle()::Opening device '%s'\n",port);
	
	portHandle = open(port, O_RDWR | O_NOCTTY | O_NDELAY);

	if (portHandle <0 ) {
		fprintf (stderr,"CLinuxSerialDriver::openSerialPort()::Error: Failed to open serial port.!\n");
		perror(port);
		return false;
	}
	return true;
}

/**
 * setCommParameters
 * @p baudrate : the communication flow rate (speed).
 * @p charsize : the character size (7,8 bits)
 * @p parity   : 0=none, 1=odd, 2=even
 * @p stopbits : the number of stop bits (1 or 2)
 *
 * @return      0 if the settings succeeded.
 *              -1 if there was an error.
 */
int CLinuxSerialDriver::setCommParameters(int baudrate,int dataBits,int parity, int stopBits ) {

	struct termios portOptions;
	int status;
	int BAUD, DATABITS, PARITY, PARITYON, STOPBITS;

	if(portHandle == -1){
		fprintf(stderr,"ERROR: CLinuxSerialDriver::setCommParameters()::The Serial port is not opened yet!!\\");
		return -1;
	}
	/*  get port options for speed, etc. */
	tcgetattr(portHandle, &portOptions);
	tcflush(portHandle, TCIFLUSH);

	/* baudrate settings */
	switch (baudrate) {
		case 115200:
			BAUD = B115200;
			break;
		case 57600:
				BAUD = B57600;
					break;
		case 38400:
			BAUD = B38400;
			break;
		case 19200:
			BAUD  = B19200;
			break;
		case 9600:
			BAUD  = B9600;
			break;
		default:
			fprintf(stderr,"CLinuxSerialDriver::setCommParameters(): Invalid Baudrate %d\n",baudrate);
			return -1;
	}

	/* databits settings */
	switch (dataBits) {
		case 8:
			DATABITS = CS8;
			break;
		case 7:
			DATABITS = CS7;
			break;
		default:
			fprintf(stderr,"CLinuxSerialDriver::setCommParameters(): Invalid databits = %d\n",dataBits);
			return -1;
	}

	/* stop bits */
	switch (stopBits) {
		case 1:
			STOPBITS = 0;
			break;
		case 2:
			STOPBITS = CSTOPB;
			break;
		default:
			fprintf(stderr,"CLinuxSerialDriver::setCommParameters(): Invalid Stopbits = %d\n",stopBits);
			return -1;

	}

	/* parity */
	switch (parity) {
		case 0: /* no parity */
			PARITYON = 0;
			PARITY = 0;
			break;
			case 1: /* odd parity */
				PARITYON = PARENB;
				PARITY = PARODD;
				break;
				case 2: /* event parity */
					PARITYON = PARENB;
					PARITY = 0;
					break;
					default: /* none */
						fprintf(stderr,"CLinuxSerialDriver::setCommParameters(): Invalid parity = %d\n",parity);
						return -1;
	}

	/* We are not setting CRTSCTS which turns on hardware flow control */
	portOptions.c_cflag = BAUD | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;

	/*  set for non-canonical (raw processing, no echo, etc.) */
	portOptions.c_iflag = IGNPAR; /* ignore parity check */
	portOptions.c_oflag = 0; /* raw output */
	portOptions.c_lflag = 0; /* raw input  */

	cfsetospeed(&portOptions, BAUD);  /* redundant with the cflag setting, above */
	cfsetispeed(&portOptions, BAUD);
	status = tcsetattr(portHandle, TCSANOW, &portOptions);

	if (status != 0)
		return -1;
	else
		return 0;
}



/*
int main(){
  return 0;
}
*/

